#ifndef __M_ROOM_H__
#define __M_ROOM_H__

#pragma once

#include "util.hpp"
#include "db.hpp"
#include "online.hpp"
#include <vector>

#define ROW 15
#define COL 15
#define WHITE_CHESS 1
#define BLACK_CHESS 2

enum room_status
{
    GAME_START,
    GAME_OVER,
};

class room
{
public:
    room(uint64_t roomid, user_table* ut, online_manager* om)
    : _roomid(roomid),
      _st(GAME_START),
      _player_count(0),
      _whiteid(0),
      _blackid(0),
      _ut(ut),
      _om(om),
      _board(ROW, std::vector<int>(COL, 0))
    {
        LOG(DEG, "room%lu create success", _roomid);
    }

    ~room()
    {
        LOG(DEG, "room%lu close success", _roomid);
    }

    uint64_t get_room_id() { return _roomid; }

    room_status get_room_status() { return _st; }

    int get_player_count() { return _player_count; }

    uint64_t get_white_id() { return _whiteid; }

    uint64_t get_black_id() { return _blackid; }

    void add_white_player(uint64_t uid) { _whiteid = uid; _player_count++; }

    void add_black_player(uint64_t uid) { _blackid = uid; _player_count++; }

    //总的请求处理函数 根据不同的请求调用不同的处理函数 得到响应进行广播
    void handle_request(Json::Value& req)
    {
        Json::Value res = req;
        if (req["room_id"].asUInt64() != _roomid)
        {
            res["result"] = false;
            res["reason"] = "房间号不匹配";
            broadcast(res);
            return;
        }
        if (req["optype"].asString() == "put_chess")
        {
            res = handle_chess(req);
            uint64_t winner_id = res["winner"].asUInt64();
            if (winner_id != 0)
            {
                uint64_t loser_id = winner_id == _whiteid ? _blackid : _whiteid;
                //更新数据库表
                _ut->win(winner_id);
                _ut->lose(loser_id);
                //更新房间状态
                _st = GAME_OVER;
            }
        }
        else if (req["optype"].asString() == "chat")
        {
            res = handle_chat(req);
        }
        else
        {
            res["result"] = false;
            res["reason"] = "未知请求类型";
        }

        broadcast(res);
    }

    Json::Value handle_chess(Json::Value& req)
    {
        Json::Value res = req;
        //判断当前发起请求的玩家是否还在房间 如果不在则另一方胜利
        uint64_t cur_uid = req["uid"].asUInt64();
        uint64_t winner_id = cur_uid == _whiteid ? _blackid : _whiteid;
        if (!_om->is_in_room(cur_uid))//TODO 需要同时判断另一个玩家是否掉线吗
        {
            res["result"] = true;
            res["reason"] = "对方掉线，不战而胜";
            res["winner"] = (Json::UInt64)winner_id;
            return res;
        }
        //获取请求中的走棋位置 判断当前走棋是否合理
        int row = req["row"].asInt(), col = req["col"].asInt();
        int color = cur_uid == _whiteid ? WHITE_CHESS : BLACK_CHESS;
        if (_board[row][col] != 0)
        {
            res["result"] = false;
            res["reason"] = "当前位置已有棋子";
            return res;
        }
        _board[row][col] = color;
        //判断是否有玩家胜利
        winner_id = isWin(row, col, color);
        res["result"] = true;
        res["winner"] = (Json::UInt64)winner_id;
        if (winner_id != 0)
        {
            res["reason"] = "对方/己方五星连珠，战无敌/虽败犹荣";
        }
        else
        {
            res["reason"] = "胜负未分/平局";
        }
        return res;
    }

    Json::Value handle_chat(Json::Value& req)
    {
        Json::Value res = req;
        //1.请求的房间号是否与当前房间的房间号匹配
        if (req["room_id"].asUInt64() != _roomid)
        {
            res["result"] = false;
            res["reason"] = "房间号不匹配";
            return res;
        }
        //2.检测消息中是否包含敏感词
        std::string msg = req["message"].asString();
        if (msg.find("垃圾") != std::string::npos)
        {
            res["result"] = false;
            res["reason"] = "消息中包含敏感词";
            return res;
        }
        res["result"] = true;
        res["message"] = msg;
        return res;
    } 

    void handle_exit(uint64_t uid)//用户连接断开时调用(websocket连接断开) 不属于请求内容
    {
        Json::Value res;
        if (_st == GAME_START)//下棋过程中退出
        {
            res["optype"] = "put_chess";
            res["result"] = true;
            res["reason"] = "对方掉线，不战而胜";
            res["room_id"] = (Json::UInt64)_roomid;
            res["uid"] = (Json::UInt64)uid;
            res["winner"] = (Json::UInt64)(uid == _whiteid ? _blackid : _whiteid);
            res["row"] = -1;
            res["col"] = -1;

            uint64_t winner_id = res["winner"].asUInt64();
            uint64_t loser_id = winner_id == _whiteid ? _blackid : _whiteid;
            //更新数据库表
            _ut->win(winner_id);
            _ut->lose(loser_id);
            //更新房间状态
            _st = GAME_OVER;

            broadcast(res);
        }
        _player_count--;
    }

    //将形成的响应广播给房间里的所有用户
    void broadcast(Json::Value& res)
    {
        //将json格式的响应序列化为json格式的字符串
        std::string body;
        json_util::serialize(res, body);

        //获取房间中所有用户的通信连接 TODO 观战用户
        wsserver_t::connection_ptr white_conn = _om->get_conn_from_room(_whiteid);
        wsserver_t::connection_ptr black_conn = _om->get_conn_from_room(_blackid);

        //广播响应
        if (white_conn)
            white_conn->send(body);
        if (black_conn)
            black_conn->send(body);
    }

private:
    uint64_t isWin(int row, int col, int color)
    {
        //横向 纵向 正斜 反斜
        if (isFive(row, col, color, 0, 1)  ||
            isFive(row, col, color, 1, 0)  ||
            isFive(row, col, color, -1, 1) ||
            isFive(row, col, color, -1, -1))
            return color == WHITE_CHESS ? _whiteid : _blackid;
        else
            return 0;
    }

    bool isFive(int row, int col, int color, int rowoff, int coloff)
    {
        int count = 1;
        int search_row = row + rowoff, search_col = col + coloff;
        while ((search_row >= 0 && search_row < ROW) && (search_col >= 0 && search_col < COL))
        {
            if (_board[search_row][search_col] != color)//一旦中途出现棋子颜色不一致则停止计数
                break;
            count++;
            search_row += rowoff;
            search_col += coloff;
        }
        search_row = row - rowoff, search_col = col - coloff;
        while ((search_row >= 0 && search_row < ROW) && (search_col >= 0 && search_col < COL))
        {
            if (_board[search_row][search_col] != color)
                break;
            count++;
            search_row -= rowoff;
            search_col -= coloff;
        }
        return count >= 5;
    }

private:
    uint64_t _roomid;
    room_status _st;
    int _player_count;
    uint64_t _whiteid;
    uint64_t _blackid;
    user_table* _ut;
    online_manager* _om;
    std::vector<std::vector<int>> _board;
};

using room_ptr = std::shared_ptr<room>;

class room_manager
{
public:
    room_manager(user_table* ut, online_manager* om)
    : _next_rid(1),
      _ut(ut),
      _om(om)
    {
        LOG(DEG, "room_manager create");
    }

    ~room_manager()
    {
        LOG(DEG, "room_manager destroy");
    }

    room_ptr create_room(uint64_t uid1, uint64_t uid2)
    {
        //两个用户在游戏大厅中对战匹配 匹配成功后创建房间

        //判断两个用户是否在大厅中(是否掉线或确认匹配)
        if (!_om->is_in_hall(uid1) && !_om->is_in_hall(uid2))
        {
            LOG(DEG, "user is not in hall");
            return room_ptr();
        }
        //创建房间并将用户信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);
        room_ptr rp(new room(_next_rid, _ut, _om));
        rp->add_white_player(uid1);
        rp->add_black_player(uid2);
        _rooms.insert(std::make_pair(_next_rid, rp));//rp为shared_ptr 当节点被删除或哈希表释放时 节点中的rp指针指向的房间信息才会被释放(引用计数为0)
        _u2r.insert(std::make_pair(uid1, _next_rid));
        _u2r.insert(std::make_pair(uid2, _next_rid));
        _next_rid++;

        return rp;
    }

    room_ptr get_room_by_rid(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if (it == _rooms.end())
        {
            LOG(DEG, "room%lu not found", rid);
            return room_ptr();
        }
        return it->second;
    }

    room_ptr get_room_by_uid(uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto uit = _u2r.find(uid);
        if (uit == _u2r.end())
        {
            LOG(DEG, "user%ld not in any room", uid);
            return room_ptr();
        }
        uint64_t rid = uit->second;
        //return get_room_by_rid(rid);//error 两个重复的锁造成死锁
        auto rit = _rooms.find(rid);
        if (rit == _rooms.end())
        {
            LOG(DEG, "room%lu not found", rid);
            return room_ptr();
        }
        return rit->second;
    }

    void close_room(uint64_t rid)
    {
        //通过房间id获取房间信息
        room_ptr rp = get_room_by_rid(rid);
        if (rp.get() == nullptr)
            return;
        //移除用户id与房间id的映射
        uint64_t uid1 = rp->get_white_id(), uid2 = rp->get_black_id();
        std::unique_lock<std::mutex> lock(_mutex);
        _u2r.erase(uid1);
        _u2r.erase(uid2);
        //移除房间信息
        _rooms.erase(rid);
    }

    //将指定用户从所在房间中移除 如果房间内没有玩家 则关闭房间(用户断开连接时调用)
    void remove_user_from_room(uint64_t uid)
    {
        //通过用户id获取房间信息
        room_ptr rp = get_room_by_uid(uid);
        if (rp.get() == nullptr)
            return;
        //将用户从房间中移除(用户退出房间)
        rp->handle_exit(uid);
        //房间中没有用户则关闭房间
        if (rp->get_player_count() == 0)
            close_room(rp->get_room_id());
    }

private:
    uint64_t _next_rid;
    user_table* _ut;
    online_manager* _om;
    std::unordered_map<uint64_t, room_ptr> _rooms;
    std::unordered_map<uint64_t, uint64_t> _u2r;
    std::mutex _mutex;
};

#endif